{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "DeAkZwDhufYA"
   },
   "source": [
    "# Open-Domain QA on Tables\n",
    "\n",
    "This tutorial shows you how to perform question-answering on tables using the `EmbeddingRetriever` or `BM25Retriever` as retriever node and the `TableReader` as reader node."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "vbR3bETlvi-3"
   },
   "source": [
    "\n",
    "## Preparing the Colab Environment\n",
    "\n",
    "- [Enable GPU Runtime](https://docs.haystack.deepset.ai/docs/enabling-gpu-acceleration#enabling-the-gpu-in-colab)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Installing Haystack\n",
    "\n",
    "To start, let's install the latest release of Haystack with `pip`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "\n",
    "pip install --upgrade pip\n",
    "pip install farm-haystack[colab,elasticsearch,metrics,inference]\n",
    "\n",
    "# Install pygraphviz for visualization of Pipelines\n",
    "apt install libgraphviz-dev\n",
    "pip install pygraphviz"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Enabling Telemetry \n",
    "Knowing you're using this tutorial helps us decide where to invest our efforts to build a better product but you can always opt out by commenting the following line. See [Telemetry](https://docs.haystack.deepset.ai/docs/telemetry) for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack.telemetry import tutorial_running\n",
    "\n",
    "tutorial_running(15)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Logging\n",
    "\n",
    "We configure how logging messages should be displayed and which log level should be used before importing Haystack.\n",
    "Example log message:\n",
    "INFO - haystack.utils.preprocessing -  Converting data/tutorial1/218_Olenna_Tyrell.txt\n",
    "Default log level in basicConfig is WARNING so the explicit parameter is not necessary but can be changed easily:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import logging\n",
    "\n",
    "logging.basicConfig(format=\"%(levelname)s - %(name)s -  %(message)s\", level=logging.WARNING)\n",
    "logging.getLogger(\"haystack\").setLevel(logging.INFO)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Start an Elasticsearch server\n",
    "You can start Elasticsearch on your local machine instance using Docker. If Docker is not readily available in your environment (eg., in Colab notebooks), then you can manually download and execute Elasticsearch from source."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Recommended: Start Elasticsearch using Docker via the Haystack utility function\n",
    "from haystack.utils import launch_es\n",
    "\n",
    "launch_es()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Start an Elasticsearch server in Colab\n",
    "\n",
    "If Docker is not readily available in your environment (e.g. in Colab notebooks), then you can manually download and execute Elasticsearch from source."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "\n",
    "wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.9.2-linux-x86_64.tar.gz -q\n",
    "tar -xzf elasticsearch-7.9.2-linux-x86_64.tar.gz\n",
    "chown -R daemon:daemon elasticsearch-7.9.2\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "%%bash --bg\n",
    "\n",
    "sudo -u daemon -- elasticsearch-7.9.2/bin/elasticsearch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "RmxepXZtwQ0E"
   },
   "outputs": [],
   "source": [
    "# Connect to Elasticsearch\n",
    "import os\n",
    "import time\n",
    "from haystack.document_stores import ElasticsearchDocumentStore\n",
    "\n",
    "\n",
    "# Wait 30 seconds only to be sure Elasticsearch is ready before continuing\n",
    "time.sleep(30)\n",
    "\n",
    "# Get the host where Elasticsearch is running, default to localhost\n",
    "host = os.environ.get(\"ELASTICSEARCH_HOST\", \"localhost\")\n",
    "\n",
    "document_index = \"document\"\n",
    "document_store = ElasticsearchDocumentStore(host=host, username=\"\", password=\"\", index=document_index)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "fFh26LIlxldw"
   },
   "source": [
    "## Add Tables to DocumentStore\n",
    "To quickly demonstrate the capabilities of the `EmbeddingRetriever` and the `TableReader` we use a subset of 1000 tables and text documents from a dataset we have published in [this paper](https://arxiv.org/abs/2108.04049).\n",
    "\n",
    "Just as text passages, tables are represented as `Document` objects in Haystack. The content field, though, is a pandas DataFrame instead of a string."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "nM63uwbd8zd6"
   },
   "outputs": [],
   "source": [
    "# Let's first fetch some tables that we want to query\n",
    "# Here: 1000 tables from OTT-QA\n",
    "from haystack.utils import fetch_archive_from_http\n",
    "\n",
    "doc_dir = \"data/tutorial15\"\n",
    "s3_url = \"https://s3.eu-central-1.amazonaws.com/deepset.ai-farm-qa/datasets/documents/table_text_dataset.zip\"\n",
    "fetch_archive_from_http(url=s3_url, output_dir=doc_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "SKjw2LuXxlGh",
    "outputId": "92c67d24-d6fb-413e-8dd7-53075141d508"
   },
   "outputs": [],
   "source": [
    "# Add the tables to the DocumentStore\n",
    "import json\n",
    "from haystack import Document\n",
    "import pandas as pd\n",
    "\n",
    "\n",
    "def read_tables(filename):\n",
    "    processed_tables = []\n",
    "    with open(filename) as tables:\n",
    "        tables = json.load(tables)\n",
    "        for key, table in tables.items():\n",
    "            current_columns = table[\"header\"]\n",
    "            current_rows = table[\"data\"]\n",
    "            current_df = pd.DataFrame(columns=current_columns, data=current_rows)\n",
    "            document = Document(content=current_df, content_type=\"table\", id=key)\n",
    "            processed_tables.append(document)\n",
    "\n",
    "    return processed_tables\n",
    "\n",
    "\n",
    "tables = read_tables(f\"{doc_dir}/tables.json\")\n",
    "document_store.write_documents(tables, index=document_index)\n",
    "\n",
    "# Showing content field and meta field of one of the Documents of content_type 'table'\n",
    "print(tables[0].content)\n",
    "print(tables[0].meta)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "hmQC1sDmw3d7"
   },
   "source": [
    "## Initialize Retriever, Reader & Pipeline\n",
    "\n",
    "### Retriever\n",
    "\n",
    "Retrievers help narrowing down the scope for the Reader to a subset of tables where a given question could be answered.\n",
    "They use some simple but fast algorithm.\n",
    "\n",
    "**Here:** We specify an embedding model that is finetuned so it can also generate embeddings for tables (instead of just text).\n",
    "\n",
    "**Alternatives:**\n",
    "\n",
    "- `BM25Retriever` that uses BM25 algorithm\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "EY_qvdV6wyK5"
   },
   "outputs": [],
   "source": [
    "from haystack.nodes.retriever import EmbeddingRetriever\n",
    "\n",
    "retriever = EmbeddingRetriever(document_store=document_store, embedding_model=\"deepset/all-mpnet-base-v2-table\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "jasi1RM2zIJ7"
   },
   "outputs": [],
   "source": [
    "# Add table embeddings to the tables in DocumentStore\n",
    "document_store.update_embeddings(retriever=retriever)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "XM-ijy6Zz11L"
   },
   "outputs": [],
   "source": [
    "## Alternative: BM25Retriever\n",
    "# from haystack.nodes.retriever import BM25Retriever\n",
    "# retriever = BM25Retriever(document_store=document_store)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "YHfQWxVI0N2e",
    "outputId": "1d8dc4d2-a184-489e-defa-d445d76c458f"
   },
   "outputs": [],
   "source": [
    "# Try the Retriever\n",
    "retrieved_tables = retriever.retrieve(\"Who won the Super Bowl?\", top_k=5)\n",
    "\n",
    "# Get highest scored table\n",
    "print(retrieved_tables[0].content)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "zbwkXScm2-gy"
   },
   "source": [
    "### Reader\n",
    "The `TableReader` is based on TaPas, a transformer-based language model capable of grasping the two-dimensional structure of a table. It scans the tables returned by the retriever and extracts the anser. The available TableReader models can be found [here](https://huggingface.co/models?pipeline_tag=table-question-answering&sort=downloads).\n",
    "\n",
    "**Notice**: The `TableReader` will return an answer for each table, even if the query cannot be answered by the table. Furthermore, the confidence scores are not useful as of now, given that they will *always* be very high (i.e. 1 or close to 1)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "4APcRoio2RxG"
   },
   "outputs": [],
   "source": [
    "from haystack.nodes import TableReader\n",
    "\n",
    "reader = TableReader(model_name_or_path=\"google/tapas-base-finetuned-wtq\", max_seq_len=512)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ILuAXkyN4F7x",
    "outputId": "4bd19dcb-df8e-4a4d-b9d2-d34650e9e5c2"
   },
   "outputs": [],
   "source": [
    "# Try the TableReader on one Table\n",
    "\n",
    "table_doc = document_store.get_document_by_id(\"36964e90-3735-4ba1-8e6a-bec236e88bb2\")\n",
    "print(table_doc.content)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "ilbsecgA4vfN",
    "outputId": "f845f43e-43e8-48fe-d0ef-91b17a5eff0e"
   },
   "outputs": [],
   "source": [
    "from haystack.utils import print_answers\n",
    "\n",
    "prediction = reader.predict(query=\"Who played Gregory House in the series House?\", documents=[table_doc])\n",
    "print_answers(prediction, details=\"all\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "jkAYNMb7R9qu"
   },
   "source": [
    "The offsets in the `offsets_in_document` and `offsets_in_context` field indicate the table cells that the model predicts to be part of the answer. They need to be interpreted on the linearized table, i.e., a flat list containing all of the table cells."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "It8XYT2ZTVJs",
    "outputId": "7d31af60-e04a-485d-f0ee-f29592b03928"
   },
   "outputs": [],
   "source": [
    "print(f\"Predicted answer: {prediction['answers'][0].answer}\")\n",
    "print(f\"Meta field: {prediction['answers'][0].meta}\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "pgmG7pzL5ceh"
   },
   "source": [
    "### Pipeline\n",
    "The Retriever and the Reader can be sticked together to a pipeline in order to first retrieve relevant tables and then extract the answer.\n",
    "\n",
    "**Notice**: Given that the `TableReader` does not provide useful confidence scores and returns an answer for each of the tables, the sorting of the answers might be not helpful."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "G-aZZvyv4-Mf"
   },
   "outputs": [],
   "source": [
    "# Initialize pipeline\n",
    "from haystack import Pipeline\n",
    "\n",
    "table_qa_pipeline = Pipeline()\n",
    "table_qa_pipeline.add_node(component=retriever, name=\"EmbeddingRetriever\", inputs=[\"Query\"])\n",
    "table_qa_pipeline.add_node(component=reader, name=\"TableReader\", inputs=[\"EmbeddingRetriever\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "m8evexnW6dev",
    "outputId": "40514084-f516-4f13-fb48-6a55cb578366"
   },
   "outputs": [],
   "source": [
    "prediction = table_qa_pipeline.run(\"When was Guilty Gear Xrd : Sign released?\", params={\"top_k\": 30})\n",
    "print_answers(prediction, details=\"minimum\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "4CBcIjIq_uFx"
   },
   "outputs": [],
   "source": [
    "# Add 500 text passages to our document store.\n",
    "\n",
    "\n",
    "def read_texts(filename):\n",
    "    processed_passages = []\n",
    "    with open(filename) as passages:\n",
    "        passages = json.load(passages)\n",
    "        for key, content in passages.items():\n",
    "            document = Document(content=content, content_type=\"text\", id=key)\n",
    "            processed_passages.append(document)\n",
    "\n",
    "    return processed_passages\n",
    "\n",
    "\n",
    "passages = read_texts(f\"{doc_dir}/texts.json\")\n",
    "document_store.write_documents(passages, index=document_index)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "j1TaNF7SiKgH"
   },
   "outputs": [],
   "source": [
    "document_store.update_embeddings(retriever=retriever, update_existing_embeddings=False)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {
    "id": "c2sk_uNHj0DY"
   },
   "source": [
    "## Pipeline for QA on Combination of Text and Tables\n",
    "We are using one node for retrieving both texts and tables, the `EmbeddingRetriever`. In order to do question-answering on the Documents coming from the `EmbeddingRetriever`, we need to route Documents of type `\"text\"` to a `FARMReader` (or alternatively `TransformersReader`) and Documents of type `\"table\"` to a `TableReader`.\n",
    "\n",
    "To achieve this, we make use of two additional nodes:\n",
    "- `RouteDocuments`: Splits the List of Documents retrieved by the `EmbeddingRetriever` into two lists containing only Documents of type `\"text\"` or `\"table\"`, respectively.\n",
    "- `JoinAnswers`: Takes Answers coming from two different Readers (in this case `FARMReader` and `TableReader`) and joins them to a single list of Answers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Ej_j8Q3wlxXE"
   },
   "outputs": [],
   "source": [
    "from haystack.nodes import FARMReader, RouteDocuments, JoinAnswers\n",
    "\n",
    "text_reader = FARMReader(\"deepset/roberta-base-squad2\")\n",
    "# In order to get meaningful scores from the TableReader, use \"deepset/tapas-large-nq-hn-reader\" or\n",
    "# \"deepset/tapas-large-nq-reader\" as TableReader models. The disadvantage of these models is, however,\n",
    "# that they are not capable of doing aggregations over multiple table cells.\n",
    "table_reader = TableReader(\"deepset/tapas-large-nq-hn-reader\")\n",
    "route_documents = RouteDocuments()\n",
    "join_answers = JoinAnswers()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Zdq6JnF5m3aP"
   },
   "outputs": [],
   "source": [
    "text_table_qa_pipeline = Pipeline()\n",
    "text_table_qa_pipeline.add_node(component=retriever, name=\"EmbeddingRetriever\", inputs=[\"Query\"])\n",
    "text_table_qa_pipeline.add_node(component=route_documents, name=\"RouteDocuments\", inputs=[\"EmbeddingRetriever\"])\n",
    "text_table_qa_pipeline.add_node(component=text_reader, name=\"TextReader\", inputs=[\"RouteDocuments.output_1\"])\n",
    "text_table_qa_pipeline.add_node(component=table_reader, name=\"TableReader\", inputs=[\"RouteDocuments.output_2\"])\n",
    "text_table_qa_pipeline.add_node(component=join_answers, name=\"JoinAnswers\", inputs=[\"TextReader\", \"TableReader\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 540
    },
    "id": "K4vH1ZEnniut",
    "outputId": "85aa17a8-227d-40e4-c8c0-5d0532faa47a"
   },
   "outputs": [],
   "source": [
    "# Remove the following comment to generate the structure of the combined Table an Text QA pipeline.\n",
    "# text_table_qa_pipeline.draw(\"pipeline.png\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![image](https://github.com/deepset-ai/haystack-tutorials/blob/main/tutorials/img/table-qa-pipeline.png?raw=true)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "strPNduPoBLe"
   },
   "outputs": [],
   "source": [
    "# Example query whose answer resides in a text passage\n",
    "predictions = text_table_qa_pipeline.run(query=\"Who was Thomas Alva Edison?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "9YiK75tSoOGA",
    "outputId": "bd52f841-3846-441f-dd6f-53b02111691e"
   },
   "outputs": [],
   "source": [
    "# We can see both text passages and tables as contexts of the predicted answers.\n",
    "print_answers(predictions, details=\"minimum\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "QYOHDSmLpzEg"
   },
   "outputs": [],
   "source": [
    "# Example query whose answer resides in a table\n",
    "predictions = text_table_qa_pipeline.run(query=\"Which country does the film Macaroni come from?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "4kw53uWep3zj",
    "outputId": "b332cc17-3cb8-4e20-d79d-bb4cf656f277"
   },
   "outputs": [],
   "source": [
    "# We can see both text passages and tables as contexts of the predicted answers.\n",
    "print_answers(predictions, details=\"minimum\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Evaluation\n",
    "To evaluate our pipeline, we can use haystack's evaluation feature. We just need to convert our labels into `MultiLabel` objects and the `eval` method will do the rest."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from haystack import Label, MultiLabel, Answer\n",
    "\n",
    "\n",
    "def read_labels(filename, tables):\n",
    "    processed_labels = []\n",
    "    with open(filename) as labels:\n",
    "        labels = json.load(labels)\n",
    "        for table in tables:\n",
    "            if table.id not in labels:\n",
    "                continue\n",
    "            label = labels[table.id]\n",
    "            label = Label(\n",
    "                query=label[\"query\"],\n",
    "                document=table,\n",
    "                is_correct_answer=True,\n",
    "                is_correct_document=True,\n",
    "                answer=Answer(answer=label[\"answer\"]),\n",
    "                origin=\"gold-label\",\n",
    "            )\n",
    "            processed_labels.append(MultiLabel(labels=[label]))\n",
    "    return processed_labels\n",
    "\n",
    "\n",
    "table_labels = read_labels(f\"{doc_dir}/labels.json\", tables)\n",
    "passage_labels = read_labels(f\"{doc_dir}/labels.json\", passages)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "eval_results = text_table_qa_pipeline.eval(table_labels + passage_labels, params={\"top_k\": 10})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Calculating and printing the evaluation metrics\n",
    "print(eval_results.calculate_metrics())"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Adding tables from PDFs\n",
    "It can sometimes be hard to provide your data in form of a pandas DataFrame. For this case, we provide the `ParsrConverter` wrapper that can help you to convert, for example, a PDF file into a document that you can index.\n",
    "\n",
    "**Attention: `parsr` needs a docker environment for execution, but Colab doesn't support docker.**\n",
    "**If you have a local docker environment, you can uncomment and run the following cells.**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import time\n",
    "\n",
    "# !docker run -d -p 3001:3001 axarev/parsr\n",
    "# time.sleep(30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# !wget https://www.w3.org/WAI/WCAG21/working-examples/pdf-table/table.pdf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# from haystack.nodes import ParsrConverter\n",
    "\n",
    "# converter = ParsrConverter()\n",
    "\n",
    "# docs = converter.convert(\"table.pdf\")\n",
    "\n",
    "# tables = [doc for doc in docs if doc.content_type == \"table\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# print(tables)"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "name": "Tutorial15_TableQA.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.9"
  },
  "vscode": {
   "interpreter": {
    "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
